from sqlalchemy import desc, select
from sqlalchemy.exc import DBAPIError, IntegrityError
from sqlalchemy.orm import joinedload

from poker_bot.core.exceptions.common import RepoError
from poker_bot.core.interfaces.withdraw import WithdrawReader, WithdrawRepo
from poker_bot.core.models.dto.withdraw import WithdrawDTO, WithdrawWithDetailsDTO
from poker_bot.core.models.enums.withdraw import WithdrawStatus
from poker_bot.database.converters.withdraw import (
    convert_db_model_to_withdraw_dto,
    convert_db_model_to_withdraw_entity,
    convert_db_model_to_withdraw_with_details_dto,
    convert_withdraw_entity_to_db_model,
)
from poker_bot.database.exception_mapper import exception_mapper
from poker_bot.database.models import Withdraw
from poker_bot.database.repositories.base import SQLAlchemyRepo
from poker_bot.domain.withdraw import WithdrawEntity


class WithdrawReaderImpl(SQLAlchemyRepo, WithdrawReader):
    @exception_mapper
    async def get_withdraw_by_id(self, withdraw_id: int) -> WithdrawDTO | None:
        """Получаем пользователя по id"""

        withdraw: Withdraw | None = await self._session.scalar(
            select(Withdraw)
            .where(
                Withdraw.id == withdraw_id,
            )
            .options(joinedload(Withdraw.payment_method))
        )
        if not withdraw:
            return None

        return convert_db_model_to_withdraw_with_details_dto(withdraw)

    @exception_mapper
    async def get_all_withdrawals(self) -> list[WithdrawWithDetailsDTO]:
        query = select(Withdraw).options(joinedload(Withdraw.payment_method))

        res = await self._session.scalars(query)
        withdrawals: list[Withdraw] = list(res)

        return [
            convert_db_model_to_withdraw_with_details_dto(withdraw)
            for withdraw in withdrawals
        ]

    @exception_mapper
    async def get_withdrawals_for_confirmation(self) -> list[WithdrawWithDetailsDTO]:
        query = (
            select(Withdraw)
            .where(Withdraw.status == WithdrawStatus.on_moderation)
            .order_by(desc(Withdraw.created_at))
            .options(joinedload(Withdraw.payment_method))
        )

        res = await self._session.scalars(query)
        withdrawals: list[Withdraw] = list(res)

        return [
            convert_db_model_to_withdraw_with_details_dto(withdraw)
            for withdraw in withdrawals
        ]


class WithdrawRepoImpl(SQLAlchemyRepo, WithdrawRepo):
    async def acquire_withdraw_by_id(self, withdraw_id: int) -> WithdrawEntity | None:
        """Получаем пользователя по id"""

        withdraw: Withdraw | None = await self._session.scalar(
            select(Withdraw)
            .where(
                Withdraw.id == withdraw_id,
            )
            .with_for_update()
        )
        if not withdraw:
            return None

        return convert_db_model_to_withdraw_entity(withdraw)

    @exception_mapper
    async def create_withdraw(self, withdraw: WithdrawEntity) -> WithdrawEntity:
        db_withdraw = convert_withdraw_entity_to_db_model(withdraw)
        self._session.add(db_withdraw)
        try:
            await self._session.flush((db_withdraw,))
        except IntegrityError as err:
            self._parse_error(err)

        return convert_db_model_to_withdraw_entity(db_withdraw)

    @exception_mapper
    async def update_withdraw(self, withdraw: WithdrawEntity) -> None:
        db_withdraw = convert_withdraw_entity_to_db_model(withdraw)
        try:
            await self._session.merge(db_withdraw)
        except IntegrityError as err:
            self._parse_error(err)

    @staticmethod
    def _parse_error(err: DBAPIError) -> None:
        raise RepoError from err
